#include "ocin_router_vc_fifo.h"

/* Single VC FIFO
 * Stores flits & VC control state.
 */

 
void ocin_router_vc_fifo::init(ocin_router *parent, 
                               const string &_name,
                               int buffers, 
                               int vc_idx,
                               bool is_injector,
                               bool is_ejector) {
	stringstream *ss = new stringstream();
	*ss << parent->node_info->name.c_str() << "." << _name;
	name = ss->str();
	delete ss;
	
	parent_router = parent;
	credit_port = NULL;
	
	// VC identifier: class, index, ID 
	vc_index = vc_idx;
	vc_id = vc_index;    // replaced by unique intra-router number @ ocin_router

        vc_out_index = 0;    // just to reduce difference in traces
	
	// initialize credits
	_buf_size = buffers;
	credits = _buf_size;
	
	// initialize control state
	empty = true;
	avail = true;
	has_vc = false;
	needs_vc = false;
	needs_xb = false;
	
	// local port is the last port in the router
	_local_port = parent_router->node_info->port_count;
	
	// is this FIFO in the ejector?
	_is_ejector = is_ejector;
	_is_injector = is_injector;
	
	// init the monitor
	mon = new ocin_mon_vc_fifo();
	stringstream s;
	s << name << "_MON";
	mon->init(s.str(), buffers);
	
	
#ifdef DEBUG
        {
          stringstream tmp;
          tmp << "Initialized.";
          ocin_name_debug(name,tmp.str());
        }
#endif

}


/* enque()
 *  - Stores a flit in VC fifo and updates control info, if it's a new head
 */
void ocin_router_vc_fifo::enque(ocin_flit *flit_in) {
	if (credits <= 0) {
          
          stringstream tmp;
          tmp   << "Enque w/ 0 or negative credits. \n"     
                << "\t Credits = " << credits << "\n"
                << "\t data.size() = " << data.size() << "\n"
                << "\t fifo depth = " << _buf_size << endl;
          ocin_name_fatal(name,tmp.str());
          exit(0);
	}
	
#ifdef PARANOID
	if (flit_in->is_header          &&
	    !param_aggressive_vc_alloc  &&
	    !is_empty()                 &&
	    !_is_injector) 
	{
	    	stringstream tmp;
	    	tmp << "Attempted to enque into a non-empty VC! \n"
	    	    << "\t header = " << flit_in->is_header << endl
	    	    << "\t conservative VC alloc = " << (!param_aggressive_vc_alloc) << endl
	    	    << "\t VC empty = " << is_empty() << endl;
	    	ocin_name_fatal(name,tmp.str());
	    	exit(0);
	}
#endif
	
	data.push_back(flit_in);
	credits--;
	// no credit update is necessary, as the upstream router does the decrement
	
	// update VC control info if new msg is at the head of the fifo
	if (data.size() == 1) {
		// sanity check: if this is a header flit, 
		// fifo should be empty & no VC should be alloc'd
		if ((flit_in->is_header) &&
			  ((!empty) || has_vc)) 
				{
                          stringstream tmp;
                          tmp  << "Header flit received but FIFO is either not empty or shows VC alloc'd \n"
                               << "\t empty = " << empty << "\n"
                               << "\t has_vc = " << has_vc << "\n";     
                          ocin_name_fatal(name,tmp.str());
                            
                          exit(0);
			    }
		// request VC & X-bar, as necessary, and update stats
		update_fifo_head();
	}
	avail = (flit_in->is_tail) ? true : false;
    empty = false;

#ifdef DEBUG
  stringstream tmp;
  tmp << "Enque " << print();
  ocin_name_debug(name, tmp.str());
#endif
}

/* mydeque()
 *  - Removes a flit from the fifo
 *  - cleans up control info, if the flit was a tail
 *  - updates control info if an SOP is in the fifo 
 */
ocin_flit* ocin_router_vc_fifo::mydeque() {
	ocin_flit *flit = data.front();
	data.pop_front();
	
	// update credit count at this VC
	// (upstream credits are generated by X-bar allocator)
	credits++;
	
	// update VC control info
	if (flit->is_tail) {  // tail sent -> free the VC
		has_vc = false;
	}
	
	if (data.empty()) {  // fifo is empty
	    empty = true;
	}
	else {               // new flit in the front of the fifo
		update_fifo_head();
	}
	
	// signal this deque to the monitor (used for deadlock monitoring)
	//mon->reset_deque_cycle_counter();  // done in update_fifo_head()

#ifdef DEBUG
  stringstream tmp;
  tmp << "Deque " << print();
  ocin_name_debug(name, tmp.str());
#endif
	
	return flit;
}


/* update_fifo_head()
 *  - update control & stats once the head of the fifo changes
 *    (as a result of flit deque or enque into an empty fifo) 
 */
void ocin_router_vc_fifo::update_fifo_head() {
	ocin_flit *flit = data.front();
	ocin_msg *msg = flit->msg;
	
	if (flit->is_header) {  // new msg at the head of VC fifo?
		needs_vc = true;
		
		// reset the deque cycle counter
		mon->reset_deque_cycle_counter();
		
		// if destination router (network iface) reached, record the time stamp
		if (!_is_ejector) {
			//is this the destination router?
			if ( (msg->dst).compare(parent_router->node_info->name) == 0) {
				msg->dst_time = ocin_cycle_count;
			} 
		}
	}
	needs_xb = true;
	
	// is this router the destination?
	if (_is_ejector) {
		flit->dst_time = ocin_cycle_count;
	}
}


/* print()
 */ 
string ocin_router_vc_fifo::print() {
  
  stringstream tmp;
  tmp << name << "(VC id: " << vc_id << ") status: \n";
  tmp << "\t occupancy = " << data.size() << "\n";
  tmp << "\t empty = " << empty << ", full = " << is_full() << "\n";
  tmp << "\t avail = " << avail << "\n";
  tmp << "\t needs VC = " << needs_vc << "\n";
  tmp << "\t needs XB = " << needs_xb << "\n";
    
  // print flit & msg @ the head of the fifo
/*
  if (data.size() > 0) {
    cout << "\t VC FIFO content (front flit): \n";
    print_flit(front_flit());
    cout << "\t VC FIFO content (msg corresponding to the flit): \n";
    print_msg(front_msg(), false);
    
  }
*/
  return tmp.str();  
}
